/*
 * TCSS 422, Project 2 (Scheduler Simulation)
 * Team 9 Name: blueraccoon422
 * Team 9 Group Members: Jacob Hall, Jacob Mertens, Diana Gorun, Alex Stringham
 * File Name: CPU.c
 * Date: May 22, 2013
 */

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
//#include <windows.h>
#include <pthread.h>
#include "CPU.h"
#include "SystemTimer.h"
#include "Queue.h"
#include "Process.h"
#include <unistd.h>
#include "Dispatcher.h"
//types of Queues
#define READY_QUEUE 0
#define IO_KB_QUEUE 2

//interrupt and handler constants
#define TIMER_INTERRUPT 0
#define KEYBOARD_REQUEST 1
#define KEYBOARD_INTERRUPT 2
#define DISK_REQUEST 3
#define DISK_INTERRUPT 4
#define VIDEO_REQUEST 5
#define VIDEO_INTERRUPT 6
#define MUTEX 7
#define SIGNAL 8
#define SIGNAL_WAIT 9

// Quantum time
#define TIMER_INTERRUPT_COUNTER 50

int cycles_run = 0; //This is incremented every cycle.  Currently set by the CLOCK_DELAY #define in SystemTimer.c
//to 100ms.  To make it delay 1 sec, set to 1000, to make it run quicker, decrease from 100
int PC = 0;			//the Program Counter for the program
int running = 1;	//flag that will get set to 0 (not running) when PC = cycles
int interrupt_process_id = 0;

static int cycles;//the number input by the user to specify the number of cycles to run the program
static int num_total_proc;//total number of processes input by the user that will run
static int num_keybd_proc;  //number of keyboard processes
static int num_io_proc;		//number of I/O processes
static int num_pc_proc;		//number of Producer/Consumer processes

//static PCB *pcb_list;
static CPU *new_cpu;

Queue *ready_queue;
Queue *interrupt_queue;
Queue *keyboard_queue;
Queue *aux_io_queue;

static pthread_cond_t buff_not_full;
static pthread_cond_t buff_not_empty;
static pthread_mutex_t mutex;
static pthread_mutex_t queue_lock;
static pthread_attr_t attr;
static pthread_attr_t detach;

/*
 * create the CPU with given parameters.
 */
int createCPUProcessor(int the_cpu_cycles, int the_num_total_proc,
		int the_num_keybd_proc, int the_num_io_proc, int the_num_pc_proc) {

	new_cpu = malloc(sizeof(*new_cpu));		//dynamic mem allocation
	cycles = the_cpu_cycles;				//assign the UI specs to the values
	num_total_proc = the_num_total_proc;	//the CPU needs to begin
	num_keybd_proc = the_num_keybd_proc;
	num_io_proc = the_num_io_proc;
	num_pc_proc = the_num_pc_proc;
	createQueues();
	initializeCPUProcesses();				//sets up all the initial processes
	createPThreads();
	runCPU();
	return 0;
}

/*
 * This will be the main while loop that will represent the running of the CPU.  It will run as long as the
 * "running" flag is set to 1, reset (running = 0) when the number of cycles_run reaches the value set by the
 * user that is stored in cycles.  (number of cycles to run the program)
 */
int createQueues(void) {
	ready_queue = new_queue(READY_QUEUE);
	interrupt_queue = new_queue(INTERRUPT_QUEUE);
	keyboard_queue = new_queue(IO_KB_QUEUE);
	aux_io_queue = new_queue(AUX_QUEUE);

	int i = num_total_proc;
	int k = num_keybd_proc;
	int io = num_io_proc;
	int p = num_pc_proc;
	int proc;
	ProcessPtr prs;
	RequestPtr requestArray;
	int pid = 0;
	while (i > 0) {
		proc = rand() % 3;
		if (proc == 1 && k > 0) {
			requestArray = request_array_init(KEYBOARD_PROCESS, 1, 150);
			prs = process_init(150, 5, requestArray, KEYBOARD_PROCESS, "KEYBOARD",
					pid, 67, 1, i);
			PCB_init(prs,0);
			pid++;
			k--;
			i--;
		} else if (proc == 1 && io > 0) {
			requestArray = request_array_init(IO_PROCESS, 1, 5);
			prs = process_init(5, 5, requestArray, IO_PROCESS, "IO", pid, 67, 1,
					i);
			PCB_init(prs,0);
			pid++;
			io--;
			i--;
		} else if (proc == 2 && p > 0) {
			requestArray = request_array_init(CALCULATOR_PROCESS, 1, 5);
			prs = process_init(5, 5, requestArray, CALCULATOR_PROCESS, "PC",
					pid, 67, 1, i);
			PCB_init(prs,0);
			pid++;
			p--;
			i--;
		}
	}
	//print_queue(ready_queue);
//num_keybd_proc;
//num_io_proc;
//num_pc_proc;
	return 0;
}

int initializeCPUProcesses() {
	currentProcess = (PCBPtr) malloc(sizeof(PCBStr));
	new_dispatch();
	return 0;
}

void runProcess() {
	if (currentProcess->next_step == currentProcess->process->no_steps) {
		//print_queue(ready_queue);
		new_dispatch();
	} else {
		//printf("P%i Step %i\n",currentProcess->process->id,currentProcess->next_step);
		currentProcess->next_step++;
	}
}

int runCPU() {
	int timer_interrupt = TIMER_INTERRUPT_COUNTER;
	while (running == 1) {
		if (timer_interrupt == 0) {
			interrupt_process_id = TIMER_INTERRUPT;
			timer_interrupt = TIMER_INTERRUPT_COUNTER; //reset back to this value and count down again
			dispatch(TIMER_INTERUPT);
		}
		if (interrupt_process_id != 0) {
			switch (interrupt_process_id) {
			case TIMER_INTERRUPT:
				printf("Timer interrupt occurred.\n");
				break;
			case KEYBOARD_INTERRUPT:
				printf("Keyboard interrupt occurred.\n");
				break;
			default:
				printf("Unknown interrupt");
				break;
			}
		}

		//increments the program counter by 1
		runProcess();
		incrementSystemPC(); //The program counter is incremented here.  This is where the time delay is introduced
							 //for the simulation.  Go to SystemTimer to adjust that time delay.  PC can be
							 //dynamically assigned in the simulation depending on what process wants to run
							 //and won't necessarily run sequentially
		cycles_run++;//this number keeps track of the number of cycles run in the program so far
		timer_interrupt--;
		if (cycles_run == cycles) {
			running = 0;
		}
	}
	return 0;
}

/*
 * Calls the SystemTimer which increments the Program Counter (PC) by 1
 */
int incrementSystemPC() {
	incrementPC(&PC);
//TODO Remove PC count?
//printf("PC = %i\n", PC);
	return 0;
}

/*
 * set up the PThreads.
 *
 * See for reference:
 * www.sourceware.org/pthreads-win32/manual/pthread_attr_init.html
 * http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_cond_init.html
 * http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_mutex_init.html
 * http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html
 */
int createPThreads() {
	pthread_cond_init(&buff_not_full, NULL );	//set up buff_not_full cond var
	pthread_cond_init(&buff_not_empty, NULL );	//set up buff_not_empty cond var
	pthread_mutex_init(&mutex, NULL );					//set up mutex lock
	pthread_mutex_init(&queue_lock, NULL );	//set up the mutex lock for the queue
	pthread_attr_init(&attr);	//initializes the thread attribute object
								//attr and fills it with default values for
								//the attributes
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //In the detached state, the
//thread's resources are released immediately
//when it terminates.
	pthread_attr_init(&detach);
	pthread_attr_setdetachstate(&detach, PTHREAD_CREATE_DETACHED);
//	//TODO: Remove puts..
	//printf("pthreads created\n");
	return 0;
}

/*
 * method will create the queues needed.
 */
